#pragma once


#include <string.h>
#include <mutex>

namespace pi {

typedef std::mutex Mutex;
typedef std::mutex MutexRW;
typedef std::unique_lock<MutexRW> ReadMutex;
typedef std::unique_lock<MutexRW> WriteMutex;

template <typename BufType=uint8_t, int buf_size=4096>
class RingBuffer
{
public:
    /**
     * @brief RingBuffer constructor
     *
     * @param size - ring buffer size
     */
    RingBuffer(int size=buf_size)
        :data(nullptr), dataSize(size), readIdx(0), writeIdx(0)
    {
        data = new BufType[size];
    }

    virtual ~RingBuffer()
    {
        if( data ) {
            delete[] data;
            data = nullptr;
        }
    }

    /**
     * @brief clear all contents in ring buffer
     *
     * @return
     *      0               - Success
     */
    virtual int clear()
    {
        WriteMutex lock(mutexBuf);

        readIdx = 0;
        writeIdx = 0;

        return 0;
    }

    /**
     * @brief total capability
     *
     * @return
     *      ringbuffer size
     */
    virtual int total() const
    {
        return dataSize;
    }

    /**
     * @brief return current used buffer size
     *
     * @return
     *      current used buffer size
     */
    virtual int used()
    {
        ReadMutex lock(mutexBuf);

        int tmp = writeIdx-readIdx;
        if( tmp<0 ) tmp += dataSize;

        return tmp;
    }

    /**
     * @brief Return remain unused buffer size
     *
     * @return
     *      Unused buffer size
     */
    virtual int left()
    {
        ReadMutex lock(mutexBuf);

        int tmp = readIdx-writeIdx;
        if( tmp<=0 ) tmp += dataSize;

        return tmp;
    }

    /**
     * @brief Write some contents to ringbuffer, and update ring buffer index
     *
     * @param buf               - Input contents
     * @param length            - contents length
     *
     * @return
     *      -1                  - Failed, contents too large
     *      (>0 value)          - Writen contents length
     */
    virtual int write(BufType* buf, int length=1)
    {
        if( left()<length || length<=0 ) return -1;

        {
            WriteMutex lock(mutexBuf);

            int ni = writeIdx + length;
            if( ni > dataSize ) {
                int n2 = ni - dataSize;
                int n1 = length - n2;

                memcpy(data+writeIdx*sizeof(BufType), buf, sizeof(BufType)*n1);
                memcpy(data, buf+n1*sizeof(BufType), sizeof(BufType)*n2);

                writeIdx = ni - dataSize;
            } else {
                memcpy(data+writeIdx*sizeof(BufType), buf, sizeof(BufType)*length);

                writeIdx = ni;
            }
        }

        return length;
    }

    /**
     * @brief Read contents from ring buffer & update index
     *
     * @param buf               - read buffer
     * @param length            - desired read legnth
     *
     * @return
     *      0                   - no contents to read
     *      (>0 value)          - acutall read contents
     */
    virtual int read(BufType* buf, int length=1)
    {
        int nused = used();
        if( nused < length ) length = nused;
        if( length <= 0 ) return 0;

        {
            WriteMutex lock(mutexBuf);

            int ni = readIdx + length;
            if( ni > dataSize ) {
                int n2 = ni - dataSize;
                int n1 = length - n2;

                memcpy(buf, data+readIdx*sizeof(BufType), sizeof(BufType)*n1);
                memcpy(buf+n1*sizeof(BufType), data, sizeof(BufType)*n2);

                readIdx = ni - dataSize;
            } else {
                memcpy(buf, data+readIdx*sizeof(BufType), sizeof(BufType)*length);

                readIdx = ni;
            }
        }

        return length;
    }
    
    /**
     * @brief Peek ring buffer contents, but do not update index
     *
     * @param buf               - read buffer
     * @param length            - desired read legnth (defaut is 0: get all contents)
     *
     * @return
     *      0                   - no contents to peek
     *      (>0 value)          - acutall read contents
     */
    virtual int peek(BufType* buf, int length=0)
    {
        int nused = used();
        if( length == 0 ) length = nused;
        if( length <= 0 ) return 0;
        
        {
            WriteMutex lock(mutexBuf);

            int ni = readIdx + length;
            if( ni > dataSize ) {
                int n2 = ni - dataSize;
                int n1 = length - n2;

                memcpy(buf, data+readIdx*sizeof(BufType), sizeof(BufType)*n1);
                memcpy(buf+n1*sizeof(BufType), data, sizeof(BufType)*n2);
            } else {
                memcpy(buf, data+readIdx*sizeof(BufType), sizeof(BufType)*length);
            }
        }
        
        return length;
    }
    

protected:
    BufType*    data;
    int         dataSize,
                readIdx, writeIdx;

    MutexRW     mutexBuf;
};


/**
 * @brief t_ring_buffer - RingBuffer demo & usage
 *
 * @param argc
 * @param argv
 * @return
 */
inline int t_ring_buffer(int argc, char *argv[])
{
    (void) argc; (void) argv;

    const int buf_size = 1024;
    pi::RingBuffer<char> ring_buf(buf_size);

    char buf[] = "Hello world! Test string! abcdefghijklmnopqrstuvwxyz!";
    char buf_read[buf_size];

    for(int itest = 0; itest < 1000; itest++) {
        printf("\n\n>>>test round [%4d]\n", itest);

        for(int i=0; i<150; i++) {
            int nwrite = ring_buf.write(buf, strlen(buf));
            printf("ring_buf write: %s (%d, nret=%d)\n", buf, (int)strlen(buf), nwrite);
            printf("ring_buf used: %4d, left: %4d\n", ring_buf.used(), ring_buf.left());

            if( nwrite <= 0 ) break;
        }

        printf("\n\n");

        for(int i=0; i<50; i++) {
            int npeek = ring_buf.peek(buf_read);
            printf("ring_buf peek: %s (%d)\n", buf_read, npeek);

            int nread = ring_buf.read(buf_read, 120);
            if( nread > 0 ) buf_read[nread] = 0;

            printf("ring_buf read: %s (%d)\n", buf_read, nread);
            printf("ring_buf used: %4d, left: %4d\n", ring_buf.used(), ring_buf.left());
            if( nread <= 0 ) break;
        }
    }

    return 0;
}

} // end of namespace pi

